-- livedoor Blog Helper.applescript
-- livedoor Blog Helper

--  Created by ppm on 07/01/05.

--  Copyright (c) 2007 ppm

--  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

--  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

--  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


(* ==== Outlets - UI ==== *)
property editor : missing value
property IDField : missing value
property passwordField : missing value
property templatesPopup : missing value
property rebuildOptionPopup : missing value

(* ==== Outlets - Script Object ==== *)
property webView : missing value
property blog : missing value
property account : missing value
property keychain : missing value

(* ==== Constants ==== *)
property templates : {"css", "index_tmpl", "article_tmpl", "category_tmpl", "monthly_tmpl"}
property rebuildOption : {"index", "index", "css", "monthly", "category", "rss,atom", "css,index,rss,atom,monthly,category,articles:all"}


(* ==== UI Events Handler ==== *)

on awake from nib theObject
	
	if name of theObject is "editor" then
		set editor to theObject
		call method "setFont:" of editor with parameter (call method "fontWithName:size:" of class "NSFont" with parameters {"Monaco", 10})
	end if
	
	if name of theObject is "webView" then
		set webView to theObject
		set blog to initWithWebView(webView) of blogController()
	end if
	
	if name of theObject is "IDField" then set IDField to theObject
	if name of theObject is "passwordField" then set passwordField to theObject
	if name of theObject is "templatesPopup" then set templatesPopup to theObject
	if name of theObject is "rebuildOptionPopup" then set rebuildOptionPopup to theObject
	
end awake from nib

on launched theObject
	initPref()
	set account to init() of accountManager()
	set keychain to init() of keychainManager()
	tell account to loadAccountInfo()
end launched

on clicked theObject
	
	if name of theObject is "download" then tell blog to download()
	if name of theObject is "upload" then tell blog to upload()
	if name of theObject is "rebuild" then tell blog to rebuild()
	
end clicked

on will close theObject
	
	if name of theObject is "main" then quit
	
end will close

on will quit theObject
	
	tell blog to logout()
	tell account to saveAccountInfo()
	
end will quit

(* ==== Functions ==== *)

on lso(str)
	return localized string of str
end lso

on isInteger(givenValue)
	
	set bool to true
	
	try
		get givenValue as integer
	on error
		set bool to false
	end try
	
	return bool
	
end isInteger

on split(sChr, src)
	set oldDel to AppleScript's text item delimiters
	set AppleScript's text item delimiters to sChr
	set buffer to text items of src
	set AppleScript's text item delimiters to oldDel
	return buffer
end split


on merge(mChr, src)
	set oldDel to AppleScript's text item delimiters
	set AppleScript's text item delimiters to (mChr as Unicode text)
	set buffer to (src as Unicode text)
	set AppleScript's text item delimiters to oldDel
	return buffer
end merge

on initPref()
	
	(* set initial value here with setInitialPref() *)
	setInitialPref("Account", "")
	
end initPref

on setInitialPref(prefID, defaultValue)
	tell user defaults to make new default entry at end of every default entry with properties {name:prefID, contents:defaultValue}
end setInitialPref

on pref(prefID)
	set prefValue to contents of default entry prefID of user defaults
	return prefValue
end pref

on setPref(prefID, newValue)
	set contents of default entry prefID of user defaults to newValue
end setPref

on currentTemplate()
	set anIndex to (content of templatesPopup) + 1
	-- log anIndex
	return item anIndex of templates
end currentTemplate

on currentRebuildOption()
	set anIndex to (content of rebuildOptionPopup) + 1
	return item anIndex of rebuildOption
end currentRebuildOption


(* ==== Script Objects ==== *)

on objectWrapper()
	
	script
		
		property substance : missing value
		
		on initWithSubstance(givenSubstance)
			set substance to givenSubstance
			return me
		end initWithSubstance
		
		on callMethod(givenMethod)
			return call method givenMethod of substance
		end callMethod
		
		on callMethodOfObject(givenMethod, givenObject)
			return call method givenMethod of givenObject
		end callMethodOfObject
		
		on callMethodOfClass(givenMethod, givenClass)
			return call method givenMethod of class givenClass
		end callMethodOfClass
		
		on callMethodWithParameter(givenMethod, givenParameter)
			return call method givenMethod of substance with parameter givenParameter
		end callMethodWithParameter
		
		on callMethodOfObjectWithParameter(givenMethod, givenObject, givenParameter)
			return call method givenMethod of givenObject with parameter givenParameter
		end callMethodOfObjectWithParameter
		
		on callMethodOfClassWithParameter(givenMethod, givenClass, givenParameter)
			return call method givenMethod of class givenClass with parameter givenParameter
		end callMethodOfClassWithParameter
		
	end script
	
end objectWrapper

on webViewWrapper()
	
	script
		
		property parent : objectWrapper()
		property mainFrame : missing value
		
		on initWithSubstance(givenSubstance)
			set my substance to givenSubstance
			set mainFrame to callMethod("mainFrame")
			return me
		end initWithSubstance
		
		on loadURL(givenURL)
			
			log "loading: " & givenURL
			
			set myURL to callMethodOfClassWithParameter("URLWithString:", "NSURL", givenURL)
			set urlRequest to callMethodOfClassWithParameter("requestWithURL:", "NSURLRequest", myURL)
			
			callMethodOfObjectWithParameter("loadRequest:", mainFrame, urlRequest)
			waitUntilProcessFinish()
			
			log "loaded: " & currentURL()
			
		end loadURL
		
		on currentURL()
			return callMethodOfObject("absoluteString", (callMethodOfObject("URL", (callMethodOfObject("request", currentDataSource())))))
		end currentURL
		
		on currentDataSource()
			return callMethodOfObject("dataSource", mainFrame)
		end currentDataSource
		
		on waitUntilProcessFinish()
			
			repeat
				if (call method "estimatedProgress" of my substance) is 0 then exit repeat
			end repeat
			
		end waitUntilProcessFinish
		
		on stringByEvaluatingJavaScriptFromString(givenString)
			return callMethodWithParameter("stringByEvaluatingJavaScriptFromString:", givenString)
		end stringByEvaluatingJavaScriptFromString
		
		on JSSetValueForKeyPath(givenValue, givenKeyPath)
			if not isInteger(givenValue) then set givenValue to "'" & givenValue & "'"
			-- log givenKeyPath & "=" & givenValue
			stringByEvaluatingJavaScriptFromString(givenKeyPath & "=" & givenValue)
		end JSSetValueForKeyPath
		
		on JSDo(givenKeyPath)
			return stringByEvaluatingJavaScriptFromString(givenKeyPath)
		end JSDo
		
		on JSValueForKeyPath(givenKeyPath)
			return JSDo(givenKeyPath)
		end JSValueForKeyPath
		
		on pageSource()
			set dataSource to call method "dataSource" of mainFrame
			set representation to call method "representation" of currentDataSource()
			return (call method "documentSource" of representation)
		end pageSource
		
		on DOMDocument()
			return call method "DOMDocument" of mainFrame
		end DOMDocument
		
	end script
	
end webViewWrapper

on blogController()
	
	script
		
		property parent : webViewWrapper()
		
		property loginPageURL : "http://member.livedoor.com/login/"
		property rebuildPage : "http://rebuild.blog.livedoor.com/cms/rebuild/"
		property editPageBaseURL : "http://cms.blog.livedoor.com/cms/design/edit?"
		property logoutPageURL : "http://cms.blog.livedoor.com/cms/logout"
		-- property logoutPageBaseURL : "http://cms.blog.livedoor.com/cms/logout?blog_id="
		
		on initWithWebView(givenWebView)
			continue initWithSubstance(givenWebView)
		end initWithWebView
		
		
		on download()
			
			try
				
				loadMgtPage(editPageOfTmpl(currentTemplate()))
				
				set content of editor to JSValueForKeyPath("document.getElementsByName('content').item(0).value")
				
			on error errorMessage
				display alert lso("BlogAccessError") message lso(errorMessage)
			end try
			
		end download
		
		
		on upload()
			
			try
				
				set newData to content of editor
				set continueUpload to true
				
				loadMgtPage(editPageOfTmpl(currentTemplate()))
				
				if length of newData is less than 20 then
					if (button returned of (display alert lso("UploadingTooLittleData") message lso("UploadConfirm") other button lso("Cancel"))) is lso("Cancel") then
						set continueUpload to false
					end if
				end if
				
				if continueUpload then
					
					set DOMDocument to DOMDocument()
					set onlineEditor to callMethodOfObjectWithParameter("item:", callMethodOfObjectWithParameter("getElementsByName:", DOMDocument, "content"), 0)
					set onlineEditorForm to callMethodOfObject("form", onlineEditor)
					
					callMethodOfObjectWithParameter("setValue:", onlineEditor, newData)
					waitUntilProcessFinish()
					
					callMethodOfObject("submit", onlineEditorForm)
					waitUntilProcessFinish()
					
				end if
				
			on error errorMessage
				display alert lso("BlogAccessError") message lso(errorMessage)
			end try
			
		end upload
		
		
		on rebuild()
			
			try
				
				loadMgtPage(rebuildPage)
				
				JSSetValueForKeyPath(my currentRebuildOption(), "document.getElementsByName('command').item(0).value")
				JSDo("document.getElementsByName('command').item(0).form.submit()")
				waitUntilProcessFinish()
				
			on error errorMessage
				display alert lso("BlogAccessError") message lso(errorMessage)
			end try
			
		end rebuild
		
		
		on loadMgtPage(givenPageURL)
			loadURL(givenPageURL)
			if currentURL() begins with loginPageURL then login()
		end loadMgtPage
		
		on login()
			
			--if count accounts of accountManager is not 0 then
			
			tell account
				set currentID to currentID()
				set currentPassword to currentPassword()
			end tell
			
			JSSetValueForKeyPath(currentID, "document.loginForm.livedoor_id.value")
			JSSetValueForKeyPath(currentPassword, "document.loginForm.password.value")
			JSDo("document.loginForm.submit()")
			--end if
			
			waitUntilProcessFinish()
			
			if currentURL() begins with loginPageURL then error "CannotLogin"
			
		end login
		
		on logout()
			try
				get currentURL() -- Trap!
				loadMgtPage(logoutPageURL)
				-- loadMgtPage(logoutPageBaseURL & currentBlogID() of account)
				waitUntilProcessFinish()
			end try
		end logout
		
		on editPageOfTmpl(givenTmpl)
			set params to merge("&", {"tmpl=" & givenTmpl, "blog_id=" & currentBlogID() of account, "id=" & currentEditID() of account})
			return editPageBaseURL & params
		end editPageOfTmpl
		
	end script
	
end blogController

on accountManager()
	
	script
		
		property accounts : {}
		property blogID : ""
		property editID : ""
		property customizeAndManagementPageURL : "http://cms.blog.livedoor.com/cms/design/"
		property lastAccount : ""
		
		on init()
			try
				set accounts to pref("Accounts")
			end try
			return me
		end init
		
		on currentBlogID()
			if blogID is "" or accountModified() then getBlogIDAndEditID()
			return blogID
		end currentBlogID
		
		on currentEditID()
			if editID is "" or accountModified() then getBlogIDAndEditID()
			return editID
		end currentEditID
		
		on currentID()
			set theID to string value of IDField
			if theID is "" then
				error "LoginInfoIsEmpty"
			else
				return theID
			end if
		end currentID
		
		on currentPassword()
			set thePassword to string value of passwordField
			if thePassword is "" then
				error "LoginInfoIsEmpty"
			else
				return thePassword
			end if
		end currentPassword
		
		on getBlogIDAndEditID()
			
			tell blog
				
				loadMgtPage(customizeAndManagementPageURL)
				
				set customizePageLink to ""
				set DOMDocument to DOMDocument()
				set links to callMethodOfObject("links", DOMDocument)
				set numberOfLinks to callMethodOfObject("length", links) - 1
				
				repeat with i from 0 to numberOfLinks
					set currentLink to callMethodOfObjectWithParameter("item:", links, i)
					if callMethodOfObject("innerHTML", currentLink) is lso("Customize") then set customizePageLink to callMethodOfObject("href", currentLink)
				end repeat
				
				if customizePageLink is not "" then
					set theResult to split("&id=", second item of split("blog_id=", customizePageLink))
					set blogID to first item of theResult
					set editID to second item of theResult
				else
					error "CannotGetBlogIDAndEditID"
				end if
				
			end tell
			
		end getBlogIDAndEditID
		
		on saveAccountInfo()
			
			set newAccount to string value of IDField
			set newPassword to string value of passwordField
			set newBlogID to ""
			set newEditID to ""
			
			if not accountModified() then
				set newBlogID to blogID
				set newEditID to editID
			end if
			
			if newAccount is not "" and newPassword is not "" then
				tell keychain to addKeyWithCombinedPassword(newAccount, {newPassword, newBlogID, newEditID})
			end if
			
		end saveAccountInfo
		
		on loadAccountInfo()
			
			set lastAccount to pref("Account")
			set splittedPassword to splittedPasswordWithAccount(lastAccount) of keychain
			
			if splittedPassword is not {""} then
				set string value of passwordField to first item of splittedPassword
				set blogID to second item of splittedPassword
				set editID to third item of splittedPassword
			end if
			
		end loadAccountInfo
		
		on accountModified()
			
			set _accountModified to false
			set currentAccount to string value of IDField
			
			if currentAccount is not equal to lastAccount then
				tell blog to logout()
				set lastAccount to currentAccount
				set _accountModified to true
			end if
			
			return _accountModified
			
		end accountModified
		
	end script
	
end accountManager


on keychainManager()
	
	script
		
		property keyName : "livedoor Blog Helper Account"
		property passwordSeparator : ","
		
		on init()
			return me
		end init
		
		on keyWithAccount(givenAccount)
			
			set theKey to missing value
			
			tell application "Keychain Scripting"
				set myKeys to keys of current keychain whose name is keyName -- can't use second filter like 'and account is givenAccount' because of bug.
				repeat with aKey in myKeys
					if account of aKey is givenAccount then
						set theKey to aKey
						(* for single account system,  you need from here: *)
					else
						delete aKey
						(* to here. *)
					end if
				end repeat
			end tell
			
			return theKey
			
		end keyWithAccount
		
		on passwordWithAccount(givenAccount)
			
			set thePassword to ""
			
			try
				tell application "Keychain Scripting"
					set thePassword to password of my keyWithAccount(givenAccount)
				end tell
			end try
			
			return thePassword
			
		end passwordWithAccount
		
		on splittedPasswordWithAccount(givenAccount)
			return split(passwordSeparator, passwordWithAccount(givenAccount))
		end splittedPasswordWithAccount
		
		on addKey(givenAccount, givenPassword)
			
			tell application "Keychain Scripting"
				
				set oldKey to my keyWithAccount(givenAccount)
				set oldPassword to my passwordWithAccount(givenAccount)
				
				if oldPassword is not givenPassword then
					if oldPassword is not "" then delete oldKey
					make new generic key at the end of keys with properties {name:keyName, account:givenAccount, password:givenPassword}
				end if
			end tell
			
		end addKey
		
		on addKeyWithCombinedPassword(givenAccount, givenCombinedPassword)
			addKey(givenAccount, merge(passwordSeparator, givenCombinedPassword))
		end addKeyWithCombinedPassword
		
	end script
	
end keychainManager